<?php
/**
 * rsync推送数据基类
 * @author 王彬    <wangbin@staff.sina.com.cn>
 * @filename  RsyncBase.class.php
 *
 */
@ini_set('memory_limit', '128M');
set_time_limit(0);
abstract class RsyncBase 
{
    protected $processnum = 5;// 程序限制进程数，默认为1，多进程处理时更改此处    
    
    protected $chown = 'nobody:nobody';    // 文件夹所属用户组

    protected $process_name;// 进程名

    protected $pid;// 内部进程号，多进程时用到

    private    $zombieLimit = "02:00:00";    //僵死进程默认超时时间 为2个小时

    protected $alert_mailers = array(  
                        "huhong@staff.sina.com.cn",
                        //"bitwangbin@qq.com",
                        );

    public function    __construct() 
    {
        //-------------system级--------------------//
        $this->setProcessName();
        //$this->setCurrentProcessName();
        $this->cProcessNum();
        $this->checkZombieProcess();
        //-------------system级--------------------//
        //$this->sendMailMessage('test','test');测试
        $this->mkReadyDir();
        $this->do_work();
    }
    /* 抽象方法，子类必须实现，实现父类得到子类的程序名    */
    //abstract function    setCurrentProcessName();
    /* 抽象方法，子类必须实现，负责新建目录及检查目录等    */
    abstract function mkReadyDir();
    /* 抽象方法，子类必须实现，负责处理入口    */
    abstract function do_work();

    /**
     * 检测路径是否存在并自动生成不存在的文件夹
     *
     * @param    string    $path    不包括文件名的绝对路径
     * @return    boolean
     * 成功:1    失败:0
     */
    protected    function mkDir($path) 
    {
        if(is_dir($path))
        {
            return true;
        }
        if(empty($path))
        {
            return    false;
        }
        $path =    rtrim($path, "/");
        $bpath = dirname($path);
        if(!is_dir($bpath))    
        {
            if(!$this->mkDir($bpath)) return false;
            @system("chown {$this->chown} {$bpath}");
        }
        if(!@chdir($bpath))
        {
            return false;
        }
        if(!@mkdir(basename($path),    0775))
        {
            return false;
        }
        @system("chown {$this->chown} {$path}");
        return true;
    }
    /**
     * 合并文件
     * @param $srcfile    原文文件名
     * @param $distinctFile 目标文件名
     */
    protected    function mergeFile($srcfile, $distinctFile) 
    {
        if(!file_exists($srcfile))
        {
            return false;
        }
        
        $s = "cat ".$srcfile." >> ".$distinctFile;
        $sh    = popen($s,    "r");
        if ($sh)
        {    
            $rs    = "";    
            while (!feof($sh))
            {        
                $rs    .= fgets($sh, 1024);
            }        
            pclose($sh);
            //如果有报错返回  false
            return ($rs) ? FALSE : TRUE;
        }
        return false;
    }
    /**
     * 得到指定目录下的所有文件列表
     * 如果    $fileregular 不为空，则返回正则匹配的文件列表
     *
     * @param    string    $path            目录绝对路径
     * @param    string    $fileregular    正则表达式，可为空
     * @return    array    $files            文件数组，绝对路径
     */
    protected    function getDirFile($path, $fileregular='')    
    {
        $path =    rtrim($path, "/");
        if(!is_dir($path)) 
        return array();
        $list =    @scandir($path);
        if($list===false) return false;
        $files = array();
        foreach($list as $filename)    
        {
            $file =    $path.'/'.$filename;
            if(!is_file($file))
            continue;
            if(!empty($fileregular)    && !preg_match($fileregular, $filename))
            continue;
            @array_push($files,    $file);
        }
        return $files;
    }
    /**     
     * rsync推送文件不需要密码文件
     * @param $file
     * @param $ip
     * @param $mod
     */
    protected    function rsyncFileNopwd($file,    $ip, $mod)
    {
        if(!is_file($file))    return false;
        $s = "/usr/bin/rsync -avz --timeout=20 " . $file  .    " ".  $ip .    "::" . $mod    . "    2>&1;";
        $sh    = popen($s,    "r");
        if ($sh)
        {        
            $rs    = "";    
            while (!feof($sh))
            {        
                $rs    .= fgets($sh, 1024);
            }        
            pclose($sh);
            
            
            if (!$rs)
            { 
                return false ;
            }        
            else    
            {
                $rs    = str_replace("\n", "", $rs);
                
                if (false !== strpos(strtolower($rs), 'sent') && false !== strpos(strtolower($rs), 'received'))
                {
                    echo "rsync    ok.\n";
                    return true;
                }
                else
                {
                    echo "rsync    fail.\n";
                    return false;
                }
            }
        }
        return false;
    }
    /**    
     * rsync推送文件成功后备份文件
     * @param $file
     * @param $filebak    
     */

    protected    function mvFileToBak($file,    $filebak='')
    {
        return rename($file, $filebak);
    }
    /**    
     * rsync推送文件失败后拷贝文件
     * @param $file
     * @param $filebak
     */
    protected    function cpFileToBak($file,    $filebak='')
    {
        if(empty($file))
            return false;
        if(empty($filebak))
            return false;
        $result = system("cp {$file} {$filebak} ");
        return $result;
    }
    /**
     * 检查准备推送文件是否被写锁定
     * @param $file
     * @return true    被锁  false    没有锁定
     */

    public function    isFileWriteLock( $filename )
    {
        if(!file_exists($filename))
        {
            return true;
        }
        $fp    = fopen($filename, "ab");
        // 进行排它型锁定
        if (flock($fp, LOCK_EX))
        {//没有被锁定
            flock($fp, LOCK_UN); 
            fclose($fp);
            return false; 
        }
        else
        {//被写锁定
            fclose($fp);
            return false;
        }
    }
    /**
     * 得到脚本的pid
     *
     * @return    interger
     */
    private    function getPid()
    {
        if(is_null($this->pid))    $this->pid = intval(getmypid());
        return $this->pid;
    }

    /**
     * 得到脚本的并设定$this->process_name名称
     *
     * @return    string
     */
    private    function setProcessName() 
    {
        //-------------------php反射机制得到子类的文件名-------------------------------
        $subclassname = get_class($this);
        $reflection = new ReflectionClass($subclassname);
        $this->process_name = basename($reflection->getFileName());
        if(!empty($this->process_name))
        {
            return $this->process_name;
        }
        //-----------------------------------------------------------------------------
        if(is_null($this->pid) || ""==$this->pid)    
        {
            $this->pid = $this->getPid();
        }
        if(is_null($this->process_name)    || ""==$this->process_name)
        {
            //根据pid 及 rsync 字符串 获得 运行的程序名
            echo $cmd_line = "ps -ef | grep    {$this->pid} | grep    rsync |    grep -v    grep | awk '{if(\"-\"==substr(\$9,0,1)||\"&\"==\$9||\"\"==\$9) { print \$10    } else {print \$9}}'";
            //加入rsync字符串的目的，是为了防止串号
            $cmd = @popen($cmd_line,    'r');
            $dirFileName = @fread($cmd,    512);
            @pclose($cmd);
            $dirFileName = trim($dirFileName);
            $this->process_name    = basename($dirFileName);
        }
        return $this->process_name;
    }
    /**
     * 进程上限限制
     *
     * @return    boolean
     */
    private    function cProcessNum() 
    {    
        //去掉 grep    vim     及zombie进程 (defunct)
        $cmd_line="ps -ef |    grep '{$this->process_name}' | grep    -v grep    |grep -v vim |grep -v vi |grep -v defunct |    wc -l";
        $cmd = @popen($cmd_line,    'r');
        $num = @fread($cmd,    512);
        $num +=    0;
        @pclose($cmd);
        if($num    > $this->processnum)
        {
            //进程数过少,则发送邮件报警，从1/20的数据比率中做监控
            //if(1==rand(0,19))//减少报警邮件重发率，
            {
                $subject  =    '[队列报警]进程名'.$this->process_name."负载过高";
                $message  =    "进程名:\t".$this->process_name."\n";
                $message .=    "预启动进程数:\t".$num."\n";
                $message .=    "最大进程数限制数:\t".$this->processnum."\n";
                $message .=    "队列机ip:\t".$this->getServerIp()."\n\n\n";
                $message .=    "\t\t请调整你的最大进程限制数或检查其他原因\n";
                $message .=    "\t\t希望能引起你对当前队列".$this->process_name."的重视\n";
                $message .=    "\n\t\t\t\t此为系统自动发送，不用回复。如有疑问请回复邮件至wangbin@staff.sina.com.cn 非常感谢！\n";
                $this->sendMailMessage($subject,$message);
            }
            return    false;
        }
        return true;
    }
    /**
     * 检查进程是否有僵死，有则给特定的人发送邮件提醒
     * 性能监控
     * 
     */
    private    function checkZombieProcess()
    {
        //是否有进程超时
            $is_timed_out_process =    false;
            $cmd = "ps -o etime    -p`ps -ef |    grep '{$this->process_name}' | grep    -v grep    | awk '{print \$2}'`";
            $fp=@popen($cmd,    'r');
            while (!feof($fp)) 
            {
                //得到的time格式101-19:26:25 08:25:14 00:01
                $time =    trim(fgets($fp,    4096));
                if("ELAPSED" <>    $time&&strlen($time) >=    8)
                {
                    if (strlen($time)>8    || $time>$this->zombieLimit)
                    {
                        $is_timed_out_process =    true;
                        break;
                    }
                }
            }
            @pclose($fp);
            //进程出现超时
            if($is_timed_out_process)
            {
                $subject  =    '[进程超时]进程名'.$this->process_name."出现超时";
                $message  =    "进程名:\t".$this->process_name."\n";        
                $message .=    "运行时间:\t".$time."\n";
                $message .=    "队列机ip:\t".$this->getServerIp()."\n\n\n";
                $message .=    "\t\t请检查出现进程超时原因\n";
                $message .=    "\t\t希望能引起你对当前队列".$this->process_name."的重视\n";
                $message .=    "\n\t\t\t\t此为系统自动发送，不用回复。如有疑问请回复邮件至wangbin@staff.sina.com.cn 非常感谢！\n";
                $this->sendMailMessage($subject,$message);
                return    false;
            }
            return true;
    }
    /**
     * 得到本地服务器的地址
     *
     * @return    string
     */
    public function    getServerIp()
    {
        $handle    = popen("/sbin/ifconfig|grep 'inet addr'", 'r');
        while ($s =    fgets($handle,1024))
        {
            if (preg_match("/inet addr:([0-9.]+)/",    $s,    $match))
            {        
                $ips[] = $match[1];
            }        
        }
        foreach    ($ips as $ip) 
        {
            if ($ip    != "127.0.0.1")
            {        
                 return    $ip;
            }        
        }
    }
    /**
     * 立即发送报警邮件
     * 
     * @$subject:邮件标题
     * @$message:邮件内容
     * @return    boolean
     */
    protected function sendMailMessage($subject='[队列报警]',$message) 
    {    
        if(empty($this->alert_mailers))
        {
            return false;
        }
        //待引用工厂模式，有待改造
        $mailClassFile = (dirname(dirname(dirname(__FILE__)))).'/groupblog_queue/model/Mailer.class.php';
        if(!file_exists($mailClassFile))
        {
            return false;
        }
        @include_once($mailClassFile);
        $mail = new MyMailer;
        if(is_array($this->alert_mailers))
        {
            foreach    ($this->alert_mailers as $mailer)
            {
                $mail->AddAddress($mailer);
            }
        }
        else 
        {
            return false;
        }
        $mail->Subject = $subject;
        $mail->Body    = $message;
        $mail->Send();
        return true;
    }

}

